﻿#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <termios.h>

static struct termios ori_attr, cur_attr;

static __inline
int tty_reset(void)
{
    if (tcsetattr(STDIN_FILENO, TCSANOW, &ori_attr) != 0)
        return -1;

    return 0;
}

static __inline
int tty_set(void)
{

    if (tcgetattr(STDIN_FILENO, &ori_attr))
        return -1;

    memcpy(&cur_attr, &ori_attr, sizeof(cur_attr));
    cur_attr.c_lflag &= ~ICANON;
    //        cur_attr.c_lflag |= ECHO;
    cur_attr.c_lflag &= ~ECHO;
    cur_attr.c_cc[VMIN] = 1;
    cur_attr.c_cc[VTIME] = 0;

    if (tcsetattr(STDIN_FILENO, TCSANOW, &cur_attr) != 0)
        return -1;

    return 0;
}

static __inline
int kbhit(void)
{

    fd_set rfds;
    struct timeval tv;
    int retval;

    /* Watch stdin (fd 0) to see when it has input. */
    FD_ZERO(&rfds);
    FD_SET(0, &rfds);
    /* Wait up to five seconds. */
    tv.tv_sec = 0;
    tv.tv_usec = 0;

    retval = select(1, &rfds, NULL, NULL, &tv);
    /* Don't rely on the value of tv now! */

    if (retval == -1) {
        perror("select()");
        return 0;
    }
    else if (retval)
        return 1;
    /* FD_ISSET(0, &rfds) will be true. */
    else
        return 0;
    return 0;
}

#define SNAKE_MAX_LEN 20
#define SNAKE_HEAD 'H'
#define SNAKE_BODY 'X'
#define BLANK_CELL ' '
#define SNAKE_FOOD '$'
#define WALL_CELL '*'

// 移动蛇：dx, dy
void snake_move(int, int);

// 结束游戏
void game_over(void);

// 输出字符矩阵
void print_map(void);

// 生成食物
void generate_food(void);

// 决定蛇走向，返回 'q' 代表无路可走
char where_go_next();

// 游戏结束
int gameOver = 0;

// 蛇的长度
int snakeLength = 5;

// 蛇身体的行坐标
int snakeX[SNAKE_MAX_LEN] = { 1,1,1,1,1 };

// 蛇身体的列坐标
int snakeY[SNAKE_MAX_LEN] = { 1,2,3,4,5 };

// 食物的行列坐标
int foodX = 0, foodY = 0;

// 初始地图
char map[12][12] = {
    "************",
    "*          *",
    "*       *  *",
    "*    *     *",
    "*          *",
    "*          *",
    "*   *      *",
    "*          *",
    "*       *  *",
    "*    *     *",
    "*          *",
    "************"
};

int main(void) {
    printf("\033[2J");
    // 设置终端进入非缓冲状态
    int tty_set_flag;
    tty_set_flag = tty_set();

    srand((unsigned int)time(NULL));
    char input;
    generate_food();
    print_map();
    while (!gameOver) {
        sleep(1);
        input = where_go_next();
        switch (input) {
        case 'A': // 向左移动
        case 'a':
            snake_move(0, -1);
            break;
        case 'D': // 向右移动
        case 'd':
            snake_move(0, 1);
            break;
        case 'W': // 向上移动
        case 'w':
            snake_move(-1, 0);
            break;
        case 'S': // 向下移动
        case 's':
            snake_move(1, 0);
            break;
        case 'Q':
        case 'q':
            gameOver = 1;
            break;
        default:
            continue;
        }
        printf("\033[2J");
        print_map();
    }

    // 恢复终端设置
    printf("Game Over!!\n");

    if (tty_set_flag == 0)
        tty_reset();

    return 0;
}

void game_over(void) {
    gameOver = 1;
}

void snake_move(int dx, int dy) {
    // 计算蛇头的新位置
    int i, x = snakeX[snakeLength - 1] + dx, y = snakeY[snakeLength - 1] + dy;

    // 判断是否吃到食物
    if (x == foodX && y == foodY) {
        // 若蛇超过最长长度则游戏结束
        if (snakeLength + 1 == SNAKE_MAX_LEN) {
            game_over();
            return;
        }
        snakeX[snakeLength] = x;
        snakeY[snakeLength] = y;
        snakeLength++;
        generate_food();
        return;
    }

    // 判断是否撞到障碍物
    if (map[x][y] == '*') {
        game_over();
        return;
    }

    // 判断是否撞到自身
    for (i = 1; i < snakeLength; i++) {
        if (snakeX[i] == x && snakeY[i] == y) {
            game_over();
            return;
        }
    }

    // 更新蛇的位置
    for (i = 0; i < snakeLength - 1; i++) {
        snakeX[i] = snakeX[i + 1];
        snakeY[i] = snakeY[i + 1];
    }
    snakeX[snakeLength - 1] = x;
    snakeY[snakeLength - 1] = y;
}

void print_map(void) {
    int i, j, k;
    char ch;
    for (i = 0; i < 12; i++) {
        for (j = 0; j < 12; j++) {
            ch = map[i][j];
            for (k = 0; k < snakeLength; k++) {
                if (snakeX[k] == i && snakeY[k] == j) {
                    ch = k == snakeLength - 1 ? SNAKE_HEAD : SNAKE_BODY;
                }
                if (i == foodX && j == foodY) {
                    ch = SNAKE_FOOD;
                }
            }
            printf("%c", ch);
        }
        printf("\n");
    }
    printf("----------------------\n");
}

void generate_food(void) {
    int canPlaceFood, i;
    do {
        canPlaceFood = 1;
        foodX = rand() % 10 + 1;
        foodY = rand() % 10 + 1;
        if (map[foodX][foodY] == '*') {
            canPlaceFood = 0;
        }
        for (i = 0; i < snakeLength; i++) {
            if (snakeX[i] == foodX && snakeY[i] == foodY) {
                canPlaceFood = 0;
                break;
            }
        }
    } while (!canPlaceFood);
}

char where_go_next() {
    char movable[] = { 'a','d','w','s' };
    // 移动方向
    int dx[] = { 0,0,-1,1 };
    int dy[] = { -1,1,0,0 };
    // 距离食物的位置
    int distance[] = { 0,0,0,0 };
    int i, j;
    for (i = 0; i < 4; i++) {
        // 计算新位置
        int x = snakeX[snakeLength - 1] + dx[i], y = snakeY[snakeLength - 1] + dy[i];
        // 计算距离食物的位置
        distance[i] = abs(foodX - x) + abs(foodY - y);
        // 不能撞到障碍物
        if (map[x][y] == '*') {
            distance[i] = 9999;
        }
        // 不能撞到自身
        for (j = 1; j < snakeLength; j++) {
            if (snakeX[j] == x && snakeY[j] == y) {
                distance[i] = 9999;
                break;
            }
        }
    }

    // 找出移动后可以使蛇头距离食物最近的方向
    int k = -1;
    int minValue = 9999;
    for (i = 0; i < 4; i++) {
        if (distance[i] < minValue) {
            k = i;
            minValue = distance[i];
        }
    }

    // 如果找不到则游戏结束
    return k == -1 ? 'q' : movable[k];
}
